package com.android.lib.download

import android.text.TextUtils
import android.util.Log
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import com.android.lib.download.entity.DownloadInfo
import com.android.lib.download.utils.*
import kotlinx.coroutines.*
import okhttp3.ResponseBody
import java.io.File
import java.io.IOException
import java.io.RandomAccessFile
import java.nio.channels.FileChannel
import java.util.concurrent.CancellationException
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext

class DownloadTask(
    var url: String
) : CoroutineScope {

    private var downloadJob: Job? = null
    private val downloadData = MutableLiveData<DownloadInfo>()

    override val coroutineContext: CoroutineContext
        get() = EmptyCoroutineContext

    init {
        launch(Dispatchers.Main) {
            var jsonStr = Cache.getString(url)
            var downloadInfo = gson.fromJson(jsonStr, DownloadInfo::class.java)
            //当缓存没有该任务记录时，创建此任务记录
            if (downloadInfo == null) {
                downloadInfo = DownloadInfo(url = url, path = Utils.getRooDir())
            }
            //正在下载的任务由于异常退出应当重置状态为暂停
            if (downloadInfo.status == DownloadInfo.DOWNLOADING) {
                downloadInfo.status = DownloadInfo.PAUSE
            }
            downloadData.postValue(downloadInfo)
        }
    }

    fun download() {
        val downloadInfo = downloadData.value
        //判断网络状态是否有连接
        if (!NetWorkUtils.isConnected(ContextHolder.application)) {
            updateTaskInfo(DownloadInfo.ERROR)
            return
        }
        downloadInfo?.run {
            if (DownloadPool.getDownloadTask(url) != null) {
                updateTaskInfo(DownloadInfo.DOWNLOADING)
            } else if (DownloadPool.getDownloadTaskMap().size >= DownloadManager.MAX_TASK) {
                updateTaskInfo(DownloadInfo.WAITING)
                return
            }
            var file = File(this.path)
            this.path = file.absolutePath
            if (file.exists() && this.currentLength == file.length()) {
                updateTaskInfo(DownloadInfo.DONE, downloadInfo)
                DownloadPool.removeDownloadTask(url)
                return
            }
            //文件名为空的时候，截取字符串获取文件名
            if (TextUtils.isEmpty(this.fileName)) {
                this.fileName = Utils.getUrlFileName(this.url)
                var file = File(this.path, this.fileName)
                this.path = file.absolutePath
            }
            launch {
                try {
                    start(downloadInfo)
                } catch (e: Throwable) {
                    Log.e("35hwm", "DownloadTask download error:${e.message}")
                    if (e !is CancellationException) {
                        updateTaskInfo(DownloadInfo.ERROR)
                    }
                } finally {
                    downloadNext()
                }
            }.run {
                downloadJob = this
            }
        }
    }

    private suspend fun start(downloadInfo: DownloadInfo) {
        withContext(Dispatchers.IO) {
            if (DownloadPool.getDownloadTask(url) == null) {
                DownloadPool.addDownloadTask(url, this@DownloadTask)
            }
            updateTaskInfo(DownloadInfo.DOWNLOADING)
            var currentLength = downloadInfo.currentLength
            val response = RetrofitDownload.downloadService.downloadFile("bytes=$currentLength-", url)
            val responseBody = response.body()
                ?: throw IOException("responseBody is null please check download url")
            val fileLength = getFileLength(currentLength, responseBody)
            downloadInfo.contentLength = fileLength
            val inputStream = responseBody.byteStream()
            val accessFile = RandomAccessFile(File(downloadInfo.path), "rwd")
            val channel = accessFile.channel
            val mappedBuffer = channel.map(
                FileChannel.MapMode.READ_WRITE,
                currentLength,
                fileLength - currentLength
            )
            val buffer = ByteArray(1024 * 4)
            var len = 0
            var lastProgress = 0
            var currentSaveLength = currentLength //当前的长度

            while (inputStream.read(buffer).also { len = it } != -1 && downloadInfo.status == DownloadInfo.DOWNLOADING && isActive) {
                mappedBuffer.put(buffer, 0, len)
                currentSaveLength += len

                val progress = (currentSaveLength.toFloat() / fileLength * 100).toInt() // 计算百分比
                if (lastProgress != progress) {
                    lastProgress = progress
                    //记录已经下载的长度
                    downloadInfo.progress = progress
                    downloadInfo.currentLength = currentSaveLength
                    updateTaskInfo(DownloadInfo.DOWNLOADING, downloadInfo)

                    if (currentSaveLength == fileLength) {
                        DownloadPool.removeDownloadTask(url)
                        downloadInfo.currentLength = fileLength
                        updateTaskInfo(DownloadInfo.DONE)
                    }
                }
            }

            inputStream.close()
            accessFile.close()
            channel.close()

        }
    }

    /**
     * 获取DownloadInfo
     */
    fun getDownloadInfo(): DownloadInfo? {
        return downloadData.value
    }

    /**
     * 添加下载任务观察者
     */
    fun observer(lifecycleOwner: LifecycleOwner, observer: Observer<DownloadInfo>) {
        downloadData.observe(lifecycleOwner, observer)
    }

    /**
     * 更新任务信息
     */
    private fun updateTaskInfo(status: Int) {
        launch(Dispatchers.Main) {
            val downloadInfo = downloadData.value
            downloadInfo?.run {
                downloadInfo.status = status
                Cache.putString(url, gson.toJson(downloadInfo))
                downloadData.postValue(downloadInfo)
            }
        }
    }

    /**
     * 更新任务信息
     */
    private fun updateTaskInfo(status: Int, downloadInfo: DownloadInfo) {
        launch(Dispatchers.Main) {
            downloadInfo.run {
                downloadInfo.status = status
                Cache.putString(url, gson.toJson(downloadInfo))
                downloadData.postValue(downloadInfo)
            }
        }
    }

    /**
     * 暂停任务
     * 只有等待中的任务和正在下载中的任务才可以进行暂停操作
     */
    fun pause() {
        cancel(CancellationException("pause"))
        val downloadInfo = downloadData.value
        downloadInfo?.run {
            if (this.status == DownloadInfo.DOWNLOADING || this.status == DownloadInfo.WAITING) {
                updateTaskInfo(DownloadInfo.PAUSE)
            }
            downloadNext()
        }
    }

    /**
     * 删除任务,删除任务会同时删除已经在数据库中保存的下载信息
     */
    fun remove() {
        launch(Dispatchers.Main) {
            cancel(CancellationException("remove"))
            val downloadInfo = downloadData.value
            downloadInfo?.reset()
            downloadData.value = downloadInfo
            withContext(Dispatchers.IO) {
                downloadInfo?.run {
                    Cache.remove(url)
                    val file = File(this.path)
                    if (file.exists()) file.delete()
                }
            }
        }
    }

    /**
     * 将会中断正在进行的下载任务
     */
    private fun cancel(cause: CancellationException) {
        downloadJob?.cancel(cause)
    }

    /**
     * 是否是等待任务
     */
    private fun isWaiting(): Boolean {
        val downloadInfo = downloadData.value
        downloadInfo ?: return false
        return downloadInfo.status == DownloadInfo.WAITING
    }

    /**
     * 下载下一个任务
     */
    private fun downloadNext() {
        for (entrySet in DownloadPool.getPrepareTaskMap()) {
            val downloadScope = entrySet.value
            if (downloadScope.isWaiting()) {
                downloadScope.download()
                break
            }
        }
    }

    /**
     * 数据总长度
     * @param currentLength Long
     * @param responseBody ResponseBody
     * @return Long
     */
    private fun getFileLength(
        currentLength: Long,
        responseBody: ResponseBody
    ) =
        if (currentLength == 0L) responseBody.contentLength() else currentLength + responseBody.contentLength()

}